package com.dlyk.config.filter;

import com.dlyk.constant.Constants;
import com.dlyk.manager.RedisStringManager;
import com.dlyk.model.User;
import com.dlyk.result.CodeEnum;
import com.dlyk.result.R;
import com.dlyk.util.JSONUtils;
import com.dlyk.util.JWTUtils;
import com.dlyk.util.ResponseUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * token验证过滤器
 */
@Component
public class TokenVerifyFilter extends OncePerRequestFilter {

    @Resource
    private RedisStringManager redisStringManager;

    // Spring Boot内置的线程池
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // 登录请求不需要token验证，此时还没有生成token，直接放行，执行下一个过滤
        if (request.getRequestURI().equals(Constants.LOGIN_URI)) {
            filterChain.doFilter(request, response);
            return;
        }

        // 获取token
        String token = null;
        if (request.getRequestURI().equals(Constants.EXPORT_CUSTOMER_EXCEL_URI)) {
            // 导出客户Excel的请求，则从请求行上获取token
            token = request.getParameter(Constants.TOKEN_NAME);
        } else {
            // 其他的请求还是从请求头中获取token
            token = request.getHeader(Constants.TOKEN_NAME);
        }

        // 如果请求token为空
        if (!StringUtils.hasText(token)) {
            // 将请求结果返回给前端
            R result = R.FAIL(CodeEnum.TOKEN_IS_EMPTY);
            // 将R对象转成json
            String resultJson = JSONUtils.toJSON(result);
            // 将R对象以json形式返回前端
            ResponseUtils.write(response, resultJson);
            return;
        }

        // 如果token校验未通过，token不匹配
        if (!JWTUtils.verifyJWT(token)) {
            // 将请求结果返回给前端
            R result = R.FAIL(CodeEnum.TOKEN_IS_ERROR);
            // 将R对象转成json
            String resultJson = JSONUtils.toJSON(result);
            // 将R对象以json形式返回前端
            ResponseUtils.write(response, resultJson);
            return;
        }

        // 从token中解析User
        User user = JWTUtils.parseUserFromJWT(token);

        // 从redis中获取token
        String redisToken = (String) redisStringManager.getValue(Constants.REDIS_JWT_KEY + user.getId());

        // 如果redis中的token已过期
        if (!StringUtils.hasText(redisToken)) {
            // 将请求结果返回给前端
            R result = R.FAIL(CodeEnum.TOKEN_IS_EXPIRED);
            // 将R对象转成json
            String resultJson = JSONUtils.toJSON(result);
            // 将R对象以json形式返回前端
            ResponseUtils.write(response, resultJson);
            return;
        }

        // 程序执行到这里，说明token验证通过

        // 设置Spring Security的安全上下文，设置认证信息（表明该用户已经登录过了）
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        // token续期，刷新token
        // 异步处理（建议使用线程池执行，也可以new一个线程 new Thread(() -> {}).start(); ）
        threadPoolTaskExecutor.execute( () -> {
            // 勾选了记住我，token的过期时间是7天，不需要刷新token时间，不影响用户的使用体验，如果刷新的话，7天的过期时间就没有意义了
            // 没有勾选记住我，过期时间是30分钟，需要刷新token时间，不然用户在正常使用系统的时候需要每隔30分钟重新登录，影响用户体验

            // 获取请求头中的信息
            String rememberMe = request.getHeader("rememberMe");
            // 没有勾选记住我，刷新token（重新设置token过期时间）
            if (!Boolean.parseBoolean(rememberMe)) {
                redisStringManager.expire(Constants.REDIS_JWT_KEY + user.getId(), Constants.DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
            }
        });

        // 放行，执行下一个过滤
        filterChain.doFilter(request, response);
    }

}

